/*
** CGA (Color Graphics Adapter) by Neil C. Obremski           [ Magenta's Maze ]
** 2017 Gibdon Moon Productions                   [ http://magsmaze.gibdon.com ]
**
** This is free and unencumbered software released into the public domain.
**
** Anyone is free to copy, modify, publish, use, compile, sell, or
** distribute this software, either in source code form or as a compiled
** binary, for any purpose, commercial or non-commercial, and by any
** means.
**
** In jurisdictions that recognize copyright laws, the author or authors
** of this software dedicate any and all copyright interest in the
** software to the public domain. We make this dedication for the benefit
** of the public at large and to the detriment of our heirs and
** successors. We intend this dedication to be an overt act of
** relinquishment in perpetuity of all present and future rights to this
** software under copyright law.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
** OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
** OTHER DEALINGS IN THE SOFTWARE.
**
** Tested w/ DOSBox 0.74, Microsoft C 5.10
 */
#ifndef __MAG_CGA
#define __MAG_CGA

#include "mag-bios.h"
#include "mag-math.h"

/* global clipping */
int X_MIN = 0;		/* minimum X screen coordinate */
int Y_MIN = 0;		/* minimum Y screen coordinate */
int X_MAX = 319;	/* maximum X screen coordinate */
int Y_MAX = 199;	/* maximum Y screen coordinate */

/* 2D point for shapes */
struct point {
	int x, y;
};

#define POINT_IS_NULL(p) (0x7FFF == (p).x)

#define POINT_NOT_NULL(p) (0x7FFF != (p).x)

#define POINT_MAKE_NULL(p) ((p).x = (p).y = 0x7FFF)

/* color constants with all bits set for 2 bytes (16-bit word) */
#define BLACK 0
#define CYAN 0x5555
#define MAGENTA 0xAAAA
#define WHITE 0xFFFF
#define CGA_MEMORY 0xB8000000L
#define PTR_CGA_MEMORY (unsigned char far*)(CGA_MEMORY)

unsigned cga_bits[4] = { BLACK, CYAN, MAGENTA, WHITE };
unsigned char cga_mask[4] = { 0xC0, 0x30, 0x0C, 0x03 };

/* global paint variables */
unsigned char far *cga_vbuf = PTR_CGA_MEMORY;
unsigned short cga_bord = 0; /* border bits */
unsigned short cga_stro = 0; /* default STROKE */
unsigned short cga_fill = 0; /* default FILL */

#define BORDER_ALL		0xF
#define BORDER_TOP		0x1
#define BORDER_LEFT		0x2
#define BORDER_RIGHT	0x4
#define BORDER_BOTTOM	0x8

#define border(bits) { cga_bord = bits; }

#define color(stroke,fill) { cga_stro = stroke; cga_fill = fill; }

#define int_abs(i) (i < 0 ? -i : i)

/* starting offset of line in memory by Y coordinate (0-199)

	Usage:
	p += cga_line[y]

	Otherwise, here is how to do the same thing WITHOUT lookup table:
	if (y & 0x1) {
		p += 0x2000;
		y &= ((unsigned int)-2); // 0xFFFE (works with 32-bit int too)
	}
	p += (y << 3);
	p += (y << 5);
*/
int cga_line[] = {
	0x0000, 0x2000, 0x0050, 0x2050, 0x00A0, 0x20A0, 0x00F0, 0x20F0,
	0x0140, 0x2140, 0x0190, 0x2190, 0x01E0, 0x21E0, 0x0230, 0x2230,
	0x0280, 0x2280, 0x02D0, 0x22D0, 0x0320, 0x2320, 0x0370, 0x2370,
	0x03C0, 0x23C0, 0x0410, 0x2410, 0x0460, 0x2460, 0x04B0, 0x24B0,
	0x0500, 0x2500, 0x0550, 0x2550, 0x05A0, 0x25A0, 0x05F0, 0x25F0,
	0x0640, 0x2640, 0x0690, 0x2690, 0x06E0, 0x26E0, 0x0730, 0x2730,
	0x0780, 0x2780, 0x07D0, 0x27D0, 0x0820, 0x2820, 0x0870, 0x2870,
	0x08C0, 0x28C0, 0x0910, 0x2910, 0x0960, 0x2960, 0x09B0, 0x29B0,
	0x0A00, 0x2A00, 0x0A50, 0x2A50, 0x0AA0, 0x2AA0, 0x0AF0, 0x2AF0,
	0x0B40, 0x2B40, 0x0B90, 0x2B90, 0x0BE0, 0x2BE0, 0x0C30, 0x2C30,
	0x0C80, 0x2C80, 0x0CD0, 0x2CD0, 0x0D20, 0x2D20, 0x0D70, 0x2D70,
	0x0DC0, 0x2DC0, 0x0E10, 0x2E10, 0x0E60, 0x2E60, 0x0EB0, 0x2EB0,
	0x0F00, 0x2F00, 0x0F50, 0x2F50, 0x0FA0, 0x2FA0, 0x0FF0, 0x2FF0,
	0x1040, 0x3040, 0x1090, 0x3090, 0x10E0, 0x30E0, 0x1130, 0x3130,
	0x1180, 0x3180, 0x11D0, 0x31D0, 0x1220, 0x3220, 0x1270, 0x3270,
	0x12C0, 0x32C0, 0x1310, 0x3310, 0x1360, 0x3360, 0x13B0, 0x33B0,
	0x1400, 0x3400, 0x1450, 0x3450, 0x14A0, 0x34A0, 0x14F0, 0x34F0,
	0x1540, 0x3540, 0x1590, 0x3590, 0x15E0, 0x35E0, 0x1630, 0x3630,
	0x1680, 0x3680, 0x16D0, 0x36D0, 0x1720, 0x3720, 0x1770, 0x3770,
	0x17C0, 0x37C0, 0x1810, 0x3810, 0x1860, 0x3860, 0x18B0, 0x38B0,
	0x1900, 0x3900, 0x1950, 0x3950, 0x19A0, 0x39A0, 0x19F0, 0x39F0,
	0x1A40, 0x3A40, 0x1A90, 0x3A90, 0x1AE0, 0x3AE0, 0x1B30, 0x3B30,
	0x1B80, 0x3B80, 0x1BD0, 0x3BD0, 0x1C20, 0x3C20, 0x1C70, 0x3C70,
	0x1CC0, 0x3CC0, 0x1D10, 0x3D10, 0x1D60, 0x3D60, 0x1DB0, 0x3DB0,
	0x1E00, 0x3E00, 0x1E50, 0x3E50, 0x1EA0, 0x3EA0, 0x1EF0, 0x3EF0
};

/* index = distance | (startbit << 2) */
unsigned char hlmask[] = {
	0,
	0xC0, /* 0 - 0 */
	0xF0, /* 0 - 1 */
	0xFC, /* 0 - 2 */
	0,
	0x30, /* 1 - 1 */
	0x3C, /* 1 - 2 */
	0x3F, /* 1 - 3 */
	0,
	0x0C, /* 2 - 2 */
	0x0F, /* 2 - 3 */
	0,
	0,
	0x03, /* 3 - 3 */
};

#ifdef NDEBUG
#define ASSERT_PIXEL(p) ;
#else
#define ASSERT_PIXEL(p) \
	if (badpixel(p)) { \
		screen(3); \
		printf("ASSERT PIXEL: %04X:%04X - %04X:%04X = %d (%s, %d)\n", \
			FP_SEG(p), FP_OFF(p), FP_SEG(cga_vbuf), FP_OFF(cga_vbuf), \
			(int)(p - cga_vbuf), __FILE__, __LINE__); \
		exit(1); \
	}
#endif

void arrow(int, int, int, int, unsigned short);
#define badpixel(p) ((p) < cga_vbuf || (p) - cga_vbuf >= 0x4000)
void blit(int, int, unsigned char far*, unsigned char far*);
void box(int, int, int, int, unsigned short);
void boxfill(int, int, int, int, unsigned short);
void cls(unsigned short);
void cls256(int, unsigned short);
void giantc(int, int, char);
void hline(int, int, int, unsigned short);
void line(int, int, int, int, unsigned short);
void pset(int, int, unsigned short);
void textblit(int, int, char*, int);
void tinyblit(int, int, char*);
void titleblit(int, int, char*, int);
void trifill(int, int, int, int, int, int, unsigned short);

/* X,Y == point in the direction of facing */
void arrow(int x, int y, int facing, int size, unsigned short c)
{
	switch (facing) {
		case 0:
			line(x-size,y+size, x,y, c);
			line(x,y, x+size,y+size, c);
			break;
		case 90:
			line(x-size,y-size, x,y, c);
			line(x,y, x-size,y+size, c);
			break;
		case 180:
			line(x-size,y-size, x,y, c);
			line(x,y, x+size,y-size, c);
			break;
		case 270:
			line(x+size,y-size, x,y, c);
			line(x,y, x+size,y+size, c);
			break;
		default: /* arbitrary rotation */
			facing += 135;
			if (facing >= 360) {
				facing -= 360;
			}
			line(x, y, x + MUL_SIN_DEG(size, facing),
				y - MUL_COS_DEG(size, facing), c);
			facing += 90;
			if (facing >= 360) {
				facing -= 360;
			}
			line(x, y, x + MUL_SIN_DEG(size, facing),
				y - MUL_COS_DEG(size, facing), c);
			break;
	};
}

/* copies pixels between two CGA-structured memory blocks */
void blit(int width, int height, unsigned char far *src, unsigned char far *dst)
{
	unsigned short far *sp = (unsigned short far*)src;
	unsigned short far *dp = (unsigned short far*)dst;

	if (INT_EVEN(height) && 256 == width) { /* special case for speed */
		while (height -= 2) {
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;

			dp += 0xFE0; sp += 0xFE0; /* (0x2000 - 0x40 / 2) */

			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;
			*dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++; *dp++ = *sp++;

			dp -= 0xFF8; sp -= 0xFF8; /* (0x2000 - 0x0F / 2) */
		}
	} else {
		/* TODO: fix blit to work with arbitrary width/height */
		screen(3);
	}
}

void box(int x1, int y1, int x2, int y2, unsigned short c)
{
	int i;

	if (y1 > y2) {
		i = y1; y1 = y2; y2 = i;
	}

	if (x1 > x2) {
		i = x1; x1 = x2; x2 = i;
	}

	hline(x1, x2, y1, c);
	if (!(y2 - y1)) {
		return;
	}
	line(x1,y1+1, x1,y2-1, c);
	line(x2,y1+1, x2,y2-1, c);
	hline(x1, x2, y2, c);
}

void boxfill(int x1, int y1, int x2, int y2, unsigned short c)
{
	int i;

	if (y1 > y2) {
		i = y1; y1 = y2; y2 = i;
	}

	if (x1 > x2) {
		i = x1; x1 = x2; x2 = i;
	}

	for ( ; y1 <= y2; y1++ ) {
		hline(x1, x2, y1, c);
	}
}

void cls(unsigned short c)
{
	unsigned short far *bp = (unsigned short far*)cga_vbuf;
	unsigned char i = 0;

	/* loop 256 times: unrolled 32x at 2 bytes a pop = 64 bytes an iteration */
	do {
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
	} while (++i);
}

void cls256(int height, unsigned short c)
{
	unsigned short far *bp = (unsigned short far*)cga_vbuf;

	while (height -= 2) {
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;

		bp += 0xFE0; /* (0x2000 - 0x40 / 2) */

		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;
		*bp++ = c; *bp++ = c; *bp++ = c; *bp++ = c;

		bp -= 0xFF8; /* (0x2000 - 0x0F / 2) */
	}
}

void line(int x1, int y1, int x2, int y2, unsigned short c)
{
	int dx;				/* horizontal distance x1 to x2 */
	int mx = 1;			/* horizontal increment */
	int dy;				/* vertical distance y1 to y2 */
	int my = 1;			/* vertical increment */
	int e = 0;			/* Bresenham error counter */
	int sx = 1;			/* horizontal starting point */
	int m;				/* masking bits */
	unsigned char far *p;/*pointer to memory when writing pixels */

	/* always draw down (swap Y2 and Y1) to match trifill() */
	if (y2 < y1) {
		if (y1 < Y_MIN || y2 > Y_MAX) {
			return;
		}
		e = y2; y2 = y1; y1 = e; e = x2; x2 = x1; x1 = e;
	} else if (y2 < Y_MIN || y1 > Y_MAX) {
		return;
	}

	if (x1 < X_MIN || x1 > X_MAX || x2 < X_MIN || x2 > X_MAX ||
		y1 < Y_MIN || y1 > Y_MAX || y2 < Y_MIN || y2 > Y_MAX) {
		if (!clipline(&x1,&y1,&x2,&y2,X_MIN,Y_MIN,X_MAX,Y_MAX)) {
			return;
		}
	}

	/* calculate axis distances */
	if (x2 < x1) {
		dx = x1 - x2 + 1; mx = -1;
	} else {
		dx = x2 - x1 + 1;
	}
	dy = y2 - y1 + 1;

	if (dx > dy) { /* horizontally biased line */

		/* keep track of LINE LENGTHS rather than plotting every pixel */
		sx = x1;
		e = dy >> 1; /* start from half-step */
		while (x1 != x2) {
			e += dy;
			if (e >= dx) {
				/* draw NEXT hline segment */
				if (sx < x1) {
					hline(sx, x1, y1, c);
				} else {
					hline(x1, sx, y1, c);
				}
				sx = x1 + mx;
				y1 += my;
				e -= dx;
			}
			x1 += mx;
		}
		/* draw LAST hline segment */
		if (sx < x1) {
			hline(sx, x1, y1, c);
		} else {
			hline(x1, sx, y1, c);
		}

	} else { /* vertically biased line */

		/* use inline put pixel with precalc bitmask and X pointer offset */
		m = cga_mask[x1&3];
		sx = (x1 >> 2);
		/* draw FIRST pixel */
		p = cga_vbuf + cga_line[y1] + sx;
		ASSERT_PIXEL(p);
		*p = *p & ~m | (c & m);
		e = dx >> 1; /* start from half-step */

		while (y1 != y2) {
			y1 += my;
			e += dx;
			if (e >= dy) {
				x1 += mx;
				m = cga_mask[x1&3];
				sx = (x1 >> 2);
				e -= dy;
			}
			/* draw NEXT pixel */
			p = cga_vbuf + cga_line[y1] + sx;
			ASSERT_PIXEL(p);
			*p = *p & ~m | (c & m);
		}
	}
}

/* blit one ASCII character at 4x magnification */
void giantc(int x, int y, char c)
{
	unsigned char far *chmem = bioschar(c);
	int fontline = 0;
	unsigned char colors[2];
	unsigned char b;
	colors[0] = (unsigned char)(cga_fill & 0xFF);
	colors[1] = (unsigned char)(cga_stro & 0xFF);

	for ( ; fontline < 8; fontline++) {
		unsigned char ch = *chmem++;
		unsigned char far *p1, far *p2, far *p3, far *p4;

		x += 28; /* start at last 8 bits and move backwards */

		p1 = cga_vbuf + cga_line[y] + (x>>2); y++;
		p2 = cga_vbuf + cga_line[y] + (x>>2); y++;
		p3 = p1 + 80; y++; p4 = p2 + 80; y++;

		*p1 = (b = colors[ch&1]); *p2 = b; *p3 = b; *p4 = b;
		x -= 4; p1--; p2--; p3--; p4--; ch >>= 1;

		*p1 = (b = colors[ch&1]); *p2 = b; *p3 = b; *p4 = b;
		x -= 4; p1--; p2--; p3--; p4--; ch >>= 1;

		*p1 = (b = colors[ch&1]); *p2 = b; *p3 = b; *p4 = b;
		x -= 4; p1--; p2--; p3--; p4--; ch >>= 1;

		*p1 = (b = colors[ch&1]); *p2 = b; *p3 = b; *p4 = b;
		x -= 4; p1--; p2--; p3--; p4--; ch >>= 1;

		*p1 = (b = colors[ch&1]); *p2 = b; *p3 = b; *p4 = b;
		x -= 4; p1--; p2--; p3--; p4--; ch >>= 1;

		*p1 = (b = colors[ch&1]); *p2 = b; *p3 = b; *p4 = b;
		x -= 4; p1--; p2--; p3--; p4--; ch >>= 1;

		*p1 = (b = colors[ch&1]); *p2 = b; *p3 = b; *p4 = b;
		x -= 4; p1--; p2--; p3--; p4--; ch >>= 1;

		*p1 = (b = colors[ch&1]); *p2 = b; *p3 = b; *p4 = b;
	}
}

void hline(int x1, int x2, int y, unsigned short c)
{
	/* cga_vbuf global defaults to 0xB8000000L; change to use memory buffer */
	unsigned char far *p = cga_vbuf;

	/* d is distance from x1 to x2 */
	int d = x2 - x1 + 1;

	/* fb is the pixel position in the first byte */
	int fb = x1 & 0x3;

	/* fl is the number of pixels in the first byte that are LIT */
	int fl = 4 - fb;

	/* m holds output from hlmask[] which is the LIT bits in a byte */
	unsigned char m;

	/* make sure x1 < x2 ... VERY expensive swap; try not to mix up your X's */
	if (d <= 0) {
		if (x2 >= x1) {
			/* this means there is overflow in the integers */
			/* hline(0, X_MAX, y, c); */
		} else {
			hline(x2, x1, y, c);
		}
		return;
	}

	/* bounds checking */
	if (y < Y_MIN || y > Y_MAX || x2 < X_MIN || x1 > X_MAX) {
		return;
	}
	if (x1 < X_MIN) {
		x1 = X_MIN;
		fb = 0;
		fl = 4;
		d = x2 - x1 + 1; /* recalculate distance */
	}
	if (x2 > X_MAX) {
		x2 = X_MAX;
		d = x2 - x1 + 1; /* recalculate distance */
	}

	/* move pointer to scanline */
	p += cga_line[y];
	ASSERT_PIXEL(p);

	/* move pointer to first byte */
	p += (x1 >> 2);
	ASSERT_PIXEL(p);

	/* partial start byte means we have to read existing value there */
	if (fb > 0) {
		if (d > fl) {
			/* line extends BEYOND this byte */
			m = hlmask[fl|(fb<<2)];
			d -= fl;
		} else {
			/* line exists ONLY within this byte */
			m = hlmask[d|(fb<<2)];
			ASSERT_PIXEL(p);
			*p = *p & ~m | (c & m);
			return;
		}
		ASSERT_PIXEL(p);
		*p = *p & ~m | (c & m); /* read and write video memory */
		p++;
	}

	if (d >= 4 && (x1 & 4)) {
		/* copy single byte if not on word boundary (every 8 pixels) */
		ASSERT_PIXEL(p);
		*p++ = (unsigned char)c;
		d -= 4;
	}

	if (d >= 8) {
		/* copy words (two bytes and 8 pixels at a time) */
		do {
			ASSERT_PIXEL(p);
			*((unsigned short far*)p) = c;
			p += sizeof(unsigned short);
			d -= 8;
		} while (d >= 8);
	}

	/* copy remaining bytes */
	for ( ; d >= 4; d -= 4) {
		ASSERT_PIXEL(p);
		*p++ = (unsigned char)c; /* write video memory */
	}

	/* partial end byte means we have to read existing value there */
	if (d > 0) {
		m = hlmask[d];
		ASSERT_PIXEL(p);
		*p = *p & ~m | (c & m); /* read and write video memory */
	}
}

/* blits text to coordinates on the screen (X,Y must be multiples of 8!) */
void textblit(int x, int y, char *buffer, int len)
{
	unsigned char c;
	unsigned short colors[2];
	colors[0] = cga_fill;
	colors[1] = cga_stro;

	if (x < 0) {
		x = 160 - (INT_MIN(strlen(buffer), len) << 2);
	}

	if (y < 0) {
		y = 96;
	}

	while ((len--) && (c = (unsigned char)(*buffer++))) {
		unsigned char far *chmem = bioschar(c);
		int i = 0;

		for ( ; i < 8; i++) {
			unsigned char fontbits = *chmem++;
			unsigned short pixbits = 0;
			pixbits |= (colors[fontbits & 0x01] & 0x0300);
			pixbits |= (colors[(fontbits & 0x02) >> 1] & 0x0C00);
			pixbits |= (colors[(fontbits & 0x04) >> 2] & 0x3000);
			pixbits |= (colors[(fontbits & 0x08) >> 3] & 0xC000);
			pixbits |= (colors[(fontbits & 0x10) >> 4] & 0x0003);
			pixbits |= (colors[(fontbits & 0x20) >> 5] & 0x000C);
			pixbits |= (colors[(fontbits & 0x40) >> 6] & 0x0030);
			pixbits |= (colors[(fontbits & 0x80) >> 7] & 0x00C0);
			*((unsigned short far*)(cga_vbuf+cga_line[y+i]+(x>>2))) = pixbits;
		}

		x += 8;
	}

	len++;

	while ((len--) > 0) {
		unsigned short far *pv = ((unsigned short far*)(
			cga_vbuf+cga_line[y]+(x>>2)));
		*pv = cga_fill; pv += 40; *pv = cga_fill; pv += 40;
		*pv = cga_fill; pv += 40; *pv = cga_fill;
		pv = ((unsigned short far*)(cga_vbuf+cga_line[y+1]+(x>>2)));
		*pv = cga_fill; pv += 40; *pv = cga_fill; pv += 40;
		*pv = cga_fill; pv += 40; *pv = cga_fill;
		x += 8;
	}
}

/* blit tiny text (4x4 characters) */
void tinyblit(int x, int y, char *buffer)
{
	static unsigned char colors[] = { 0x00, 0xAA, 0xFF, 0xFF, 0x55, 0x55 };
	unsigned char c;

	while ((c = (unsigned char)(*buffer++))) {
		unsigned char far *chmem = bioschar(c);
		int i = 0;

		for ( ; i < 4; i++) {
			unsigned char fontb1 = *chmem++;
			unsigned char fontb2 = *chmem++;
			unsigned char pixbits = 0;

			pixbits |= (colors[(fontb1 & 0x01) + ((fontb1 & 0x02)) +
				(fontb2 & 0x01) + ((fontb2 & 0x02)>>1)]) & 0x03;
			pixbits |= (colors[((fontb1 & 0x04)>>2) + ((fontb1 & 0x08)>>2) +
				((fontb2 & 0x04)>>2) + ((fontb2 & 0x08)>>3)]) & 0x0C;
			pixbits |= (colors[((fontb1 & 0x10)>>4) + ((fontb1 & 0x20)>>4) +
				((fontb2 & 0x10)>>4) + ((fontb2 & 0x20)>>5)]) & 0x30;
			pixbits |= (colors[((fontb1 & 0x40)>>6) + ((fontb1 & 0x80)>>6) +
				((fontb2 & 0x40)>>6) + ((fontb2 & 0x80)>>7)]) & 0xC0;

			*(cga_vbuf+cga_line[y+i]+(x>>2)) = pixbits;
		}

		x += 4;
	}
}

/* blit large text (8x16 characters) */
void titleblit(int x, int y, char *buffer, int len)
{
	unsigned char c;
	unsigned short colors[2];
	colors[0] = cga_fill;
	colors[1] = cga_stro;

	while ((len--) && (c = (unsigned char)(*buffer++))) {
		unsigned char far *chmem = bioschar(c);
		int i = 0;

		for ( ; i < 8; i++) {
			unsigned char fontbits = *chmem++;
			unsigned short pixbits = 0;
			pixbits |= (colors[fontbits & 0x01] & 0x0300);
			pixbits |= (colors[(fontbits & 0x02) >> 1] & 0x0C00);
			pixbits |= (colors[(fontbits & 0x04) >> 2] & 0x3000);
			pixbits |= (colors[(fontbits & 0x08) >> 3] & 0xC000);
			pixbits |= (colors[(fontbits & 0x10) >> 4] & 0x0003);
			pixbits |= (colors[(fontbits & 0x20) >> 5] & 0x000C);
			pixbits |= (colors[(fontbits & 0x40) >> 6] & 0x0030);
			pixbits |= (colors[(fontbits & 0x80) >> 7] & 0x00C0);
			*((unsigned short far*)(cga_vbuf+cga_line[y++]+(x>>2))) = pixbits;
			*((unsigned short far*)(cga_vbuf+cga_line[y++]+(x>>2))) = pixbits;
		}

		y -= 16;
		x += 8;
	}

	len++;

	while ((len--) > 0) {
		unsigned short far *pv = ((unsigned short far*)(
			cga_vbuf+cga_line[y]+(x>>2)));
		*pv = cga_fill; pv += 40; *pv = cga_fill; pv += 40;
		*pv = cga_fill; pv += 40; *pv = cga_fill; pv += 40;
		*pv = cga_fill; pv += 40; *pv = cga_fill; pv += 40;
		*pv = cga_fill; pv += 40; *pv = cga_fill;
		pv = ((unsigned short far*)(cga_vbuf+cga_line[y+1]+(x>>2)));
		*pv = cga_fill; pv += 40; *pv = cga_fill; pv += 40;
		*pv = cga_fill; pv += 40; *pv = cga_fill; pv += 40;
		*pv = cga_fill; pv += 40; *pv = cga_fill; pv += 40;
		*pv = cga_fill; pv += 40; *pv = cga_fill;
		x += 8;
	}
}

/* fills a general triangle */
void trifill(int x1, int y1, int x2, int y2, int x3, int y3, unsigned short c)
{
	int row;

	/* edges are 1-2/2-3 (top/bottom) and 1-3 (long edge) */
	int x, x1to3;	/* X' edge pixel positions */
	int dx, dx1to3;	/* Horizontal distances between vertices */
	int dy, dy1to3;	/* Vertical distances between vertices */
	int e, e1to3;	/* Edge DDA error accumulators */
	int mx, m1to3;	/* X' movement amount */

	/* sort coordinates in ascending Y order (top to bottom) */
	if (y1 > y2) {
		row = y1; y1 = y2; y2 = row; row = x1; x1 = x2; x2 = row;
	}
	if (y1 > y3) {
		row = y1; y1 = y3; y3 = row; row = x1; x1 = x3; x3 = row;
	}
	if (y2 > y3) {
		row = y2; y2 = y3; y3 = row; row = x2; x2 = x3; x3 = row;
	}

	/* basic bounds checking */
	if (y3 < Y_MIN || y1 > Y_MAX) {
		return;
	}

	/* P1 to P3 distances (top tip to bottom tip) */
	if (x3 < x1) {
		dx1to3 = x1 - x3 + 1;
		m1to3 = -1;
	} else {
		dx1to3 = x3 - x1 + 1;
		m1to3 = 1;
	}

	dy1to3 = y3 - y1 + 1;

	/* long edge (P1 to P3) X and error reset */
	x1to3 = x1;
	e1to3 = dx1to3 >> 1;

	/* expand from top tip to elbow */
	if (y1 < y2) {
		/* P1 to P2 distances (top tip to elbow) */
		if (x2 < x1) {
			dx = x1 - x2 + 1;
			mx = -1;
		} else {
			dx = x2 - x1 + 1;
			mx = 1;
		}

		dy = y2 - y1 + 1;

		/* start edge from X1 going to X2 (mid-step) */
		x = x1;
		e = dx >> 1;

		for (row = y1; row < y2; row++) {

			while (e >= dy) {
				x += mx;
				e -= dy;
			}

			if (x < x1to3) {
				hline(x, x1to3, row, c);
			} else {
				hline(x1to3, x, row, c);
			}

			e1to3 += dx1to3;
			while (e1to3 >= dy1to3) {
				x1to3 += m1to3;
				e1to3 -= dy1to3;
			}

			e += dx;
		}
	}

	/* P2 to P3 distances (elbow to bottom tip) */
	if (x3 < x2) {
		dx = x2 - x3 + 1;
		mx = -1;
	} else {
		dx = x3 - x2 + 1;
		mx = 1;
	}

	dy = y3 - y2 + 1;

	/* reset x and accumulator for new edge */
	x = x2;
	e = dx >> 1;

	/* step scanlines from elbow to bottom tip */
	for (row = y2; row <= y3; row++) {

		while (e1to3 >= dy1to3) {
			x1to3 += m1to3;
			e1to3 -= dy1to3;
		}

		if (x < x1to3) {
			hline(x, x1to3, row, c);
		} else {
			hline(x1to3, x, row, c);
		}

		e += dx;
		while (e >= dy) {
			x += mx;
			e -= dy;
		}

		e1to3 += dx1to3;
	}

	/* add border to filled triangle */
	if (cga_bord) {
		if (cga_bord & BORDER_TOP) {
			if (cga_bord & BORDER_LEFT) {
				if (x2 < x3) {
					line(x1,y1, x2,y2, cga_stro);
				} else {
					line(x1,y1, x3,y3, cga_stro);
				}
			}
			if (cga_bord & BORDER_RIGHT) {
				if (x2 > x3) {
					line(x1,y1, x2,y2, cga_stro);
				} else {
					line(x1,y1, x3,y3, cga_stro);
				}
			}
		}
		if (cga_bord & BORDER_BOTTOM) {
			if (cga_bord & BORDER_LEFT) {
				if (x1 < x2) {
					line(x1,y1, x3,y3, cga_stro);
				} else {
					line(x2,y2, x3,y3, cga_stro);
				}
			}
			if (cga_bord & BORDER_RIGHT) {
				if (x1 > x2) {
					line(x1,y1, x3,y3, cga_stro);
				} else {
					line(x2,y2, x3,y3, cga_stro);
				}
			}
		}
	}
}

void pset(int x, int y, unsigned short c)
{
	/* cga_vbuf global defaults to 0xB8000000L; change to use memory buffer */
	unsigned char far *p = cga_vbuf;

	/* cga_mask[] returns the 2 bits ON in the byte for this position */
	int m = cga_mask[x&3];

	/* bounds checking */
	if (((unsigned int)x) > X_MAX || ((unsigned int)y) > Y_MAX) {
		return;
	}

	/* move pointer to beginning of scanline */
	p += cga_line[y];

	/* move pointer to byte containing pixel (y<<4 + y<<6 == y*80) */
	p += (x >> 2);

	/* read/write video memory; c is full bit pattern for solid color */
	*p = *p & ~m | (c & m);
}

#endif
